#ifndef _QGCM_DIAGNOSTICS_SRC_
#define _QGCM_DIAGNOSTICS_SRC_
#include "diagfuncs.h"
#include "diagnostics.h"
#include "netcdfaux.h"
#include "misclib.h"
#include "model.h"
#include <stdarg.h>
#include <limits>
#include <signal.h>
#include "preprocessor.h"

#ifdef DEBUG
// ............................................................... //
// make sure the default constructor fails
model_diagnostics_block::model_diagnostics_block(){
	fatal_stop("Cannot call this constructor.");
}
#endif
// ............................................................... //
model_diagnostics_block::model_diagnostics_block(model_class & model){
	this->mo = &(model);
	this->function_attributes.numargs = -1;
	this->attributes.name = "DIAGNOSTICS";
	this->blocktype = repeatblock;
	this->attributes.allowimplicit = false;
	this->function_attributes.depth = 0;
	this->attributes.timesrunned = 0;
}
// ............................................................... //
model_class & model_diagnostics_block::getModel(){
	return * ( this->mo );
}
// ............................................................... //
bool model_diagnostics_block::isblockscript() const{
	return this->blocktype == scriptblock;
}
// ............................................................... //
bool model_diagnostics_block::isblockstartscript() const{
	return this->blocktype == startblock;
}
// ............................................................... //
bool model_diagnostics_block::isblockendscript() const{
	return this->blocktype == endblock;
}
// ............................................................... //
bool model_diagnostics_block::isblockfunction() const{
	return this->blocktype == function;
};
// ............................................................... //
const string & model_diagnostics_block::GetVariableName(int i) const{
	if(i < 0 || i > this->variables.size()){
		fatal_stop("Attempted to look up variable name of variable that does not exist.");
	}
	return variables[i].name;
};	
// ............................................................... //
const string model_diagnostics_block::getBlockAsText() const {
	int nid = (*this).debug_attributes.nid;
	int sline =(*this).debug_attributes.slinenumber;
	int eline = (*this).debug_attributes.elinenumber;
	// grab the string
	string dest;
	const namelistdata & ndata = mo->getnamelistbyid( static_cast<size_t>(nid) );
	const string & ncontents = ndata.filecontent;
	int ln = 1;
	int spos = -1;
	int epos = -1;
	for(size_t pos = 0; pos < ncontents.size(); ++pos ){
		if(ln >= sline && spos < 0) spos = pos;
		if(ln <= eline ) epos = pos;
		if(ln > eline) break;
		if( ncontents[pos] == '\n' ) ln++;
	}
	if(epos>=spos){
		dest = ncontents.substr(spos, epos-spos+1);
	}
	return dest;
}
// ............................................................... //
int model_diagnostics_block::getfuncnumargs() const{
	return this->function_attributes.numargs;
};
// ............................................................... //
const string & model_diagnostics_block::getfuncname() const{
	return this->attributes.name;
};
// ............................................................... //
void model_diagnostics_block::assignarglist(const ptr_val_type * args, const int size){
	assert(size == this->function_attributes.numargs);
	assert(this->blocktype == function || this->blocktype == scriptblock);
	for(int i = 0; i < size; i++){
		*( this->variables[i].data ) = *(args[i]->AsIValue()); 
	}
}

// ............................................................... //
char_type model_diagnostics_block::GetReturnType() const{
	return this->function_attributes.ret_type;
}
// ............................................................... //
const char * model_diagnostics_block::instruction::getname() const{
	const inst & it = this->id;
	if(it == inst_mu){
		return "inst_mu";
	}else if(it == inst_trap){
		return "inst_trap";
	}else if(it == inst_jmp){
		return "inst_jmp";
	}else if(it == inst_jmpnif){
		return "inst_jmpnif";
	}else if(it == inst_exit){
		return "inst_exit";
	}else if(it == inst_jmpif){
		return "inst_jmpif";
	}else if(it == inst_return){
		return "inst_return";
	}else{
		return "<empty>";
	}
}
// ............................................................... //
vector<char_type> model_diagnostics_block::GetCallStackTypes() const{
	// normally this function is only called once per function
	// registration
	vector<char_type> ret;
	ret.resize( this->function_attributes.numargs, 0 );
	for(int i = 0; i < this->function_attributes.numargs; i++){
		ret[i] = this->variables[i].dtype;
	}
	return ret;
};
// ............................................................... //
void model_diagnostics_block::assert_type_correctness() const{
	// used mainly during compilation
	// check variables on the current stack to ensure they are all
	// assigned valid types
	for(size_t i = 0; i < variables.size(); i++){ 
		variables[i].assert_type_correctness();
	}
};
// ............................................................... //
bool model_diagnostics_block::executeinstruction(Value & rvalue, bool & ret_statement_called,
		size_t & cinstr, bool & debug)
{
	//if( ! mo->iscompiled() ) return this->checkinstructions(rvalue, ret_statement_called, cinstr, debug);
	instruction & instruct = *(instructions[cinstr]);
	// run through debugger
	mo->mdebugger.filterblock(cinstr, debug);
	try{
		switch(instruct.id) {
			case instruction::inst_return:
				rvalue = instruct.parser->Eval();
				ret_statement_called = true;
				attributes.timesrunned = 1;
				return true;
			case instruction::inst_trap:
				call_debug_trap(); // only available in debug mode
				cinstr++; // as of now does nothing but is used for debugging.
				break;
			case instruction::inst_mu:
				instruct.parser->Eval();
				cinstr++;
				break;
			case instruction::inst_jmp:
			case instruction::inst_jmpif :
			case instruction::inst_jmpnif :
				if(instruct.id == instruction::inst_jmp){ 
					cinstr = instruct.jmpinstr; 
				}else{
					bool val = cast_to_bool(instruct.parser->Eval());
					if(instruct.id == instruction::inst_jmpif && val){ 
						cinstr = instruct.jmpinstr; 
					}else if(instruct.id == instruction::inst_jmpnif && !val){
						cinstr = instruct.jmpinstr;
					}else{
						cinstr++;
					}
				}
				break;
			case instruction::inst_exit :
				cinstr = this->instructions.size();
				break;
			default :
				fatal_stop("Unrecogonized instruction.");
				return false;
		};
		return true;
	}catch(const ParserError & e){
		mo->printfmsg("%s\n", e.what());
		if(this->blocktype == function){
			mo->printfmsg("Function '%s' ", this->attributes.name.c_str());
		}else{
			mo->printfmsg("Block '%s' ", this->attributes.name.c_str());
		}
		mo->printfmsg("aborted due to runtime error.\n" );
		if(mo->debugonerror()){
			debug = true;
			return false; // let the breakpoint loop take over for the user to see what's wrong
		}else{ // print the line number too to help with debugging ...
			mo->printfmsg("Runtime error in '%s' in statement beginning on line '%i'.\n", 
					mo->getnamelistname(static_cast<size_t>(instruct.debug_attributes.namelist)).c_str(), 
					instruct.debug_attributes.slinenumber);
		};
		attributes.timesrunned=1;
		return false;
	}
};
// ............................................................... //
bool model_diagnostics_block::runcode(Value & rvalue, bool & ret_statement_called, bool&  debug){
	// new version interprets the blocks
	bool ret_value = false;
	this->mo->mdebugger.pushblock(this);
	size_t cinstr = 0;
	while(cinstr < this->instructions.size()){
		instruction & instruct = *(instructions[cinstr]);
		if(instruct.runonce && this->attributes.timesrunned > 0){
			cinstr++;
		}else{
			if(executeinstruction(rvalue, ret_statement_called, cinstr, debug)){
				ret_value = true;
			}else if(debug){
				if(debug) mo->mdebugger.setbreakpoint();
				//try again
				executeinstruction(rvalue, ret_statement_called, cinstr, debug);
			}else{
				ret_value = false;
				break;
			}
			ret_value = true;
			if(ret_statement_called) break;
		}
	}
	this->attributes.timesrunned=1;
	this->mo->mdebugger.popblock();
	return ret_value;
};
// ............................................................... //
void model_diagnostics_block::pushscope(){
	this->function_attributes.depth++;
	if(this->function_attributes.depth > 1 && mo->iscompiled()){ // save the stack
		this->function_attributes.saved_variables.push_back( new vector<Value> );
		vector<Value>  & saved_vars = *(this->function_attributes.saved_variables.back());
		saved_vars.resize( this->variables.size(), Value(static_cast<float_type>(0.0)) );
		for(size_t i = 0; i < variables.size(); i++){
			saved_vars[i] = *(variables[i].data);
		}
	}
}
// ............................................................... //
void model_diagnostics_block::popscope(){
	if(this->function_attributes.depth > 1 && mo->iscompiled()){
		vector<Value> & saved_vars = *(this->function_attributes.saved_variables.back());
		for(size_t i = 0; i < variables.size(); i++){
			if( ! variables[i].isstatic ){
				*(variables[i].data) = saved_vars[i];
			}
		}
		delete ( this->function_attributes.saved_variables.back() );
		this->function_attributes.saved_variables.pop_back();
	}
	this->function_attributes.depth--;
};
// ............................................................... //
void model_diagnostics_block::runfunction(Value & rvalue, bool debug){
	assert(this->blocktype == function); 
	bool ret_hit = false;
	// save the call stack in the model
	//string buffer;
	//this->sprintfunctioncallinformation(buffer);
	//mo->pushfunccallsignature(buffer);

	if(! this->runcode(rvalue, ret_hit, debug) ){
		mpError("Error in function '%s'.", this->attributes.name.c_str());
	}
	if(!ret_hit){
		mpError("Control exited function '%s' without a return statement.", this->attributes.name.c_str());
	}
	// - - - - - - - - - - - - -  - -  - - - --  - some debug information
	if(debug){
		const qstream & fout = mo->getDBGOUT();
		fout.printf( "Function '%s' has exited and returned '", this->attributes.name.c_str());
		fout << rvalue;
		fout.printf( "'\n");
	}
};
// ............................................................... //
bool model_diagnostics_block::runscriptblock(bool debug){
	Value rdummy = 0;
	if(this->blocktype == scriptblock){
		bool rhit = false;
		this->runcode(rdummy, rhit, debug);
	}
	return true; // might be used in the future
};
// ............................................................... //
bool model_diagnostics_block::runstatusblock(bool debug){
	if(this->blocktype == statusblock){
		Value rdummy = 0; bool rhit = false;
		return this->runcode(rdummy, rhit, debug);
	}
	return true;
};
// ............................................................... //
bool model_diagnostics_block::runendblock(bool debug){
	if(this->blocktype == endblock){
		Value rdummy = 0; bool rhit = false;
		return this->runcode(rdummy, rhit, debug);
	}
	return true;
}
// ............................................................... //
bool model_diagnostics_block::runstartblock(bool debug){
	if(this->blocktype == startblock){
		Value rdummy = 0; bool rhit = false;
		return this->runcode(rdummy, rhit, debug);
	}
	return true;
}
// ............................................................... //
bool model_diagnostics_block::runrepeatblock(bool debug){
	if(this->blocktype == repeatblock){
		Value rdummy = 0; bool return_hit = false;
		return this->runcode(rdummy, return_hit, debug);
	}
	return true;
};
// ............................................................... //
void model_diagnostics_block::setallowimplicit(bool implicit){
	this->attributes.allowimplicit=implicit;
}
// ............................................................... //
model_diagnostics_block::~model_diagnostics_block(){
	for(size_t i = 0; i < this->instructions.size(); i++){
		delete instructions[i];
		instructions[i] = NULL;
	}
	instructions.clear();
	variables.clear();
}
// ............................................................... //
bool model_diagnostics_block::isvariabledefined(const string & name) const{
	unsigned int tsize = this->variable_scope_resolution_list.size();
	if(tsize == 0){
		for(unsigned int i = 0; i < variables.size(); i++){
			if( variables[i].name == name ){
				return true;
			}
		}
	}else{
		const vector<unsigned int> & varptrlist = variable_scope_resolution_list.top();
		for(unsigned int u = 0; u < varptrlist.size(); u++){
			if( variables[u].name == name ){
				return true;
			}
		}
	}
	return false;
}; 
// ............................................................... //
size_t  model_diagnostics_block::getvariablessize() const{
	return this->variables.size();
}
// ............................................................... //
model_diagnostics_block::instruction::instruction(){
	this->id = inst_mu;
	this->jmpinstr = -1;
	this->runonce = false;
	this->debug_attributes.namelist = -1;
	this->debug_attributes.slinenumber = -1;
	this->debug_attributes.elinenumber = -1;
	this->debug_attributes.breakpointset = false;
	this->parser = new ParserX(pckNONE);
};

model_diagnostics_block::instruction::~instruction(){
	delete this->parser;
};
// ............................................................... //
model_diagnostics_block::diagvariable::diagvariable(){
	fatal_stop("Cannot call this constructor.");
};
// ............................................................... //
void model_diagnostics_block::diagvariable::assert_type_correctness() const{
	if( data == NULL ) fatal_stop("Cannot check type correctness of null object.");
	if( ! does_value_conform_to_type( *data, this->dtype ) ){
		mpError("%s %s cannot be assigned value of type %s.", 
				get_type_id(this->dtype), 
				this->name.c_str(), get_type_id(data->GetType())
		       );
	}
};
// ............................................................... //
model_diagnostics_block::diagvariable::diagvariable(const diagvariable &ov){
	this->name = ov.name;
	this->data = ov.data;
	this->isstatic = ov.isstatic;
	this->dtype = ov.dtype;
}
// ............................................................... //
model_diagnostics_block::diagvariable & model_diagnostics_block::diagvariable::operator=(const diagvariable& ov)
{
	this->name = ov.name;
	this->data = ov.data;
	this->isstatic = ov.isstatic;
	this->dtype = ov.dtype;
	return (*this);
}
// ............................................................... //
model_diagnostics_block::diagvariable::diagvariable(const string & nname, Value * var, char_type ct, bool st){
	this->name = nname;
	this->dtype = ct;
	this->data = var;
	this->isstatic = st;
	assert(this->data != NULL);
	this->assert_type_correctness();
};

model_diagnostics_block::diagvariable::~diagvariable(){
	// we do not delete this, because the structure is a container that is copied several times within vectors
	// and therefore should not delete the original pointer ever
	// delete this->data;
};

/**************************************************************************
  class user_defined_function 
 ***************************************************************************/
	user_defined_function::user_defined_function(model_diagnostics_block * dblock, char_type rtype, const vector<char_type> & argtypes)
: ICallback( cmFUNC, dblock->getfuncname().c_str(), 1)
{
	this->diag_block = dblock;

	rassert(is_type_valid(rtype));
	this->func_sig.resize( 2 + argtypes.size(), 0 );
	this->func_sig[1] = ':';
	this->func_sig[0] = rtype;
	for(size_t i = 0; i < argtypes.size(); i++)
	{
		rassert( is_type_valid( argtypes[i] ) );
		this->func_sig[i+2] = argtypes[i];
	}
	this->SetArgc(argtypes.size());
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void user_defined_function::CheckRetType(const ptr_val_type & ret_ptr) const {
	const Value * ret =  ( ret_ptr->AsIValue()->AsValue() );
	if( ! does_value_conform_to_type( *ret, this->func_sig[0] ) ){
		mpError("function must return type %s but instead returned type %s.",
				get_type_id( func_sig[0] ), get_type_id(ret->GetType()));
	} 
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void user_defined_function::CheckArgs(const ptr_val_type * argv, const int argc) const{
	if( ! this->func_sig.size() - 2 == argc ){
		mpError("function takes %i arguments but was supplied %i arguments.",
				this->func_sig.size() - 2, argc); 
	}
	for(size_t i = 2; i < this->func_sig.size() ; i++){
		char_type ct = func_sig[i];
		const IValue & arg = * ( argv[i-2]->AsIValue() );
		if( ! does_value_conform_to_type( arg, ct ) ){
			mpError("argument %i must be of type %s but is of type %s.", 
					static_cast<int>(i-1), get_type_id(ct), get_type_id(arg.GetType()));
		}
	}
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const char_type * user_defined_function::GetProtoType() const 
{
	return this->func_sig.c_str();
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const char_type * user_defined_function::GetDesc() const{
	return "user defined diagnostics function";
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
IToken * user_defined_function::Clone() const {
	return new user_defined_function(*this);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void user_defined_function::Eval(ptr_val_type & retptr, const ptr_val_type * a_pArg, int a_iArgc){
	bool iscompiled = getModel().iscompiled();
	Value & ret = * ( retptr->AsIValue() -> AsValue() );
	if(!iscompiled) CheckArgs(a_pArg, a_iArgc);
	if( iscompiled ){
		diag_block->pushscope();
		diag_block->assignarglist( a_pArg,  a_iArgc);
		bool modelb = getModel().isbreakpointon();
		getModel().removebreakpoint();
		diag_block->runfunction( ret , modelb);
#ifdef DEBUG
		CheckRetType(retptr); 
#endif
		diag_block->popscope();
		return; 
	}
	ret = get_default_value_of_type( this->func_sig[0] );
}
/**************************************************************************
  end class user_defined_function 
 ***************************************************************************/

#endif
